#region Copyright Notice
// 
// 
//  
//  Copyright (c) 2009-2011 Anindya Chatterjee
//  
//  Licensed under the Apache License, Version 2.0 (the "License");
//  you may not use this file except in compliance with the License.
//  You may obtain a copy of the License at
//  
//      http://www.apache.org/licenses/LICENSE-2.0
//  
//  Unless required by applicable law or agreed to in writing, software
//  distributed under the License is distributed on an "AS IS" BASIS,
//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//  See the License for the specific language governing permissions and
//  limitations under the License.
// 
// 
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using AbstractClass.Extensions;
using AbstractClass.Linq;

namespace AbstractClass
{
    /// <summary>
    /// Provides a set of static methods for common statistical functions.
    /// </summary>
    public static class Stat
    {
        #region | Count |

        /// <summary>
        /// Calculates the total number of elements.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns>Total number of elements.</returns>
        public static double Count(IEnumerable<double> data)
        {
            return data.Count();
        }

        /// <summary>
        /// Calculates the total number of elements.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns>Total number of elements.</returns>
        public static double? Count(IEnumerable<double?> data)
        {
            return data.Count();
        }

        #endregion

        #region | Mean |

        /// <summary>
        /// Calculates the Mean of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <param name="type">The type of mean.</param>
        /// <returns>Mean of the specified data.</returns>
        public static double Mean(IEnumerable<double> data, MeanType type)
        {
            switch (type)
            {
                case MeanType.Arithmetic:
                    return data.Average();
                case MeanType.Geometric:
                    double gp = data.Aggregate<double, double>(1, (current, i) => current * i);
                    return gp.ToThePower(data.Count().Inverse());
                case MeanType.Harmonic:
                    double hp = data.Sum(j => 1 / j);
                    return data.Count() * hp.Inverse();
                default:
                    throw new InvalidOperationException("type");
            }
        }

        /// <summary>
        /// Calculates the Mean of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <param name="type">The type of mean.</param>
        /// <returns>Mean of the specified data.</returns>
        public static double? Mean(IEnumerable<double?> data, MeanType type)
        {
            switch (type)
            {
                case MeanType.Arithmetic:
                    return data.Average();
                case MeanType.Geometric:
                    double gp = 1;
                    long count = 0L;
                    foreach (var i in data.Where(i => i.HasValue))
                    {
                        gp *= i.GetValueOrDefault();
                        count++;
                    }
                    return count > 0 ? (double?) gp.ToThePower(data.Count().Inverse()) : null;
                case MeanType.Harmonic:
                    double hp = 0;
                    long length = 0L;
                    foreach (var j in data.Where(j => j.HasValue))
                    {
                        hp += j.GetValueOrDefault().Inverse();
                        length++;
                    }
                    return length > 0 ? (double?) (data.Count() * hp.Inverse()) : null;
                default:
                    throw new InvalidOperationException("type");
            }
        }

        /// <summary>
        /// Calculates Mean of the specified data by projecting it to a new form.
        /// </summary>
        /// <typeparam name="TSource">The type of the data.</typeparam>
        /// <param name="data">The source data.</param>
        /// <param name="selector">The selector to transform the data.</param>
        /// <param name="type">The mean type.</param>
        /// <returns>Mean of the specified data.</returns>
        public static double Mean<TSource>(IEnumerable<TSource> data, Func<TSource, double> selector, MeanType type)
        {
            return Mean(data.Select(selector), type);
        }

        /// <summary>
        /// Calculates Mean of the specified data by projecting it to a new form.
        /// </summary>
        /// <typeparam name="TSource">The type of the data.</typeparam>
        /// <param name="data">The source data.</param>
        /// <param name="selector">The selector to transform the data.</param>
        /// <param name="type">The mean type.</param>
        /// <returns>Mean of the specified data.</returns>
        public static double? Mean<TSource>(IEnumerable<TSource> data, Func<TSource, double?> selector, MeanType type)
        {
            return Mean(data.Select(selector), type);
        }

        #endregion

        #region | Median |

        /// <summary>
        /// Calculates the Median of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The median of the data.</returns>
        public static double Median(IEnumerable<double> data)
        {
            double[] dataSource = data.ToArray();
            return Percentile(dataSource, 50);
        }

        /// <summary>
        /// Calculates the Median of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The median of the data.</returns>
        public static double? Median(IEnumerable<double?> data)
        {
            double?[] dataSource = data.ToArray();
            return Percentile(dataSource, 50);
        }

        #endregion

        #region | Mode |

        /// <summary>
        /// Calculates the Mode of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <param name="count">The count.</param>
        /// <returns>The mode of the specified data.</returns>
        public static double[] Mode(IEnumerable<double> data, out int count)
        {
            var hashTable = new Hashtable();
            count = 0;
            var modevalue = new List<double>();

            foreach (double item in data)
            {
                if (!hashTable.ContainsKey(item))
                    hashTable.Add(item, 1);
                else
                    hashTable[item] = (int) hashTable[item] + 1;

                if ((int) hashTable[item] > count)
                    count = (int) hashTable[item];
            }

            foreach (DictionaryEntry entry in hashTable)
            {
                if ((int) entry.Value == count)
                    modevalue.Add((double) entry.Key);
            }
            return modevalue.ToArray();
        }

        /// <summary>
        /// Calculates the Mode of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <param name="count">The count.</param>
        /// <returns>The mode of the specified data.</returns>
        public static double?[] Mode(IEnumerable<double?> data, out int count)
        {
            var table = new Dictionary<double?, int>();
            count = 0;
            var modevalue = new List<double?>();

            foreach (var item in data.Where(item => item != null))
            {
// ReSharper disable AssignNullToNotNullAttribute
                if (!table.ContainsKey(item))
                    table.Add(item, 1);
                else
                    table[item] = table[item] + 1;

                if (table[item] > count)
                    count = table[item];
// ReSharper restore AssignNullToNotNullAttribute
            }

            foreach (var entry in table)
            {
                if (entry.Value == count)
                    modevalue.Add(entry.Key);
            }
            return modevalue.ToArray();
        }

        #endregion

        #region | Percentile |

        /// <summary>
        /// Calculates the Percentile of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <param name="percent">The percent to calculate percentile.</param>
        /// <returns>Percentile of the specified data.</returns>
        public static double Percentile(double[] data, double percent)
        {
            Array.Sort(data);

            if (percent >= 100.0d) return data[data.Length - 1];

            double position = (data.Length + 1) * percent / 100.0;
            double leftNumber, rightNumber;

            double n = percent / 100.0d * (data.Length - 1) + 1.0d;

            if (position >= 1)
            {
                leftNumber = data[(int) Math.Floor(n) - 1];
                rightNumber = data[(int) Math.Floor(n)];
            }
            else
            {
                leftNumber = data[0];
                rightNumber = data[1];
            }

            if (leftNumber == rightNumber)
                return leftNumber;
            double part = n - Math.Floor(n);
            return leftNumber + part * (rightNumber - leftNumber);
        }

        /// <summary>
        /// Calculates the Percentile of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <param name="percent">The percent to calculate percentile.</param>
        /// <returns>Percentile of the specified data.</returns>
        public static double? Percentile(double?[] data, double percent)
        {
            data.Sort();
            if (percent >= 100.0d) return data[data.Length - 1];
            double position = (data.Length + 1) * percent / 100.0;
            double? leftNumber, rightNumber;

            double n = percent / 100.0d * (data.Length - 1) + 1.0d;

            if (position >= 1)
            {
                leftNumber = data[(int) Math.Floor(n) - 1];
                rightNumber = data[(int) Math.Floor(n)];
            }
            else
            {
                leftNumber = data[0];
                rightNumber = data[1];
            }

            if (leftNumber == rightNumber)
                return leftNumber;
            double part = n - Math.Floor(n);
            return leftNumber + part * (rightNumber - leftNumber);
        }

        #endregion

        #region | Sum |

        /// <summary>
        /// Calculate the Sum of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Sum of the data.</returns>
        public static double Sum(IEnumerable<double> data)
        {
            return data.Sum();
        }

        /// <summary>
        /// Calculate the Sum of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Sum of the data.</returns>
        public static double? Sum(IEnumerable<double?> data)
        {
            return data.Sum();
        }

        #endregion

        #region | Min |

        /// <summary>
        /// Calculate the Minimum of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The minimum of the specified data.</returns>
        public static double Min(IEnumerable<double> data)
        {
            return data.Min();
        }

        /// <summary>
        /// Calculate the Minimum of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The minimum of the specified data.</returns>
        public static double? Min(IEnumerable<double?> data)
        {
            return data.Min();
        }

        #endregion

        #region | Max |

        /// <summary>
        /// Calculate the Maximum of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The maximum of the data.</returns>
        public static double Max(IEnumerable<double> data)
        {
            return data.Max();
        }

        /// <summary>
        /// Calculate the Maximum of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The maximum of the data.</returns>
        public static double? Max(IEnumerable<double?> data)
        {
            return data.Max();
        }

        #endregion

        #region | Range |

        /// <summary>
        /// Calculate the Range of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The range of the data.</returns>
        public static double Range(IEnumerable<double> data)
        {
            return data.Max() - data.Min();
        }

        /// <summary>
        /// Calculate the Range of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>The range of the data.</returns>
        public static double? Range(IEnumerable<double?> data)
        {
            return data.Max() - data.Min();
        }

        #endregion

        #region | Variance |

        /// <summary>
        /// Calculate the Variance of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Variance of the data.</returns>
        public static double Variance(IEnumerable<double> data)
        {
            double sumOfErrorSquare = SumOfErrorSquare(data);
            return sumOfErrorSquare / ((double) data.Count() - 1);
        }

        /// <summary>
        /// Calculate the Variance of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Variance of the data.</returns>
        public static double? Variance(IEnumerable<double?> data)
        {
            double? sumOfErrorSquare = SumOfErrorSquare(data);
            return sumOfErrorSquare / ((double) data.Count() - 1);
        }

        #endregion

        #region | Standard Deviation |

        /// <summary>
        /// Calculate the Standard Deviation of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Standard Deviation of the specified data.</returns>
        public static double StdDev(IEnumerable<double> data)
        {
            double variance = Variance(data);
            return Math.Sqrt(variance);
        }

        /// <summary>
        /// Calculate the Standard Deviation of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Standard Deviation of the specified data.</returns>
        public static double? StdDev(IEnumerable<double?> data)
        {
            double? variance = Variance(data);
            return Math.Sqrt(variance.GetValueOrDefault());
        }

        #endregion

        #region | Skewness |

        /// <summary>
        /// Calculate the Skewness of the specified data.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns>Skewness of the specified data.</returns>
        public static double Skewness(IEnumerable<double> data)
        {
            double mean = Mean(data, MeanType.Arithmetic);
            double stdDev = StdDev(data);
            double skewCum = 0;
            double count = data.Count();

            for (int i = 0; i < data.Count(); i++)
            {
                skewCum += Math.Pow((data.ElementAt(i) - mean) / stdDev, 3);
            }

            return count / (count - 1) / (count - 2) * skewCum;
        }

        /// <summary>
        /// Calculate the Skewness of the specified data.
        /// </summary>
        /// <param name="data">The data.</param>
        /// <returns>Skewness of the specified data.</returns>
        public static double? Skewness(IEnumerable<double?> data)
        {
            double? mean = Mean(data, MeanType.Arithmetic);
            double? stdDev = StdDev(data);
            double skewCum = 0;
            double count = data.Count();

            for (int i = 0; i < data.Count(); i++)
            {
                double? item = (data.ElementAt(i) - mean) / stdDev;
                skewCum += Math.Pow(item.GetValueOrDefault(), 3);
            }

            return count / (count - 1) / (count - 2) * skewCum;
        }

        #endregion

        #region | Kurtosis |

        /// <summary>
        /// Calculate the Kurtosis of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Kurtosis of the specified data.</returns>
        public static double Kurtosis(IEnumerable<double> data)
        {
            double mean = Mean(data, MeanType.Arithmetic);
            double m1 = 0d, m2 = 0d;
            double n = data.Count();
            for (int i = 0; i < n; i++)
            {
                double m = data.ElementAt(i) - mean;
                m1 = m1 + m.ToThePower(2);
                m2 = m2 + m.ToThePower(4);
            }

            double m3 = m1.ToThePower(2);
            double kurtosis = ((n + 1) * n * (n - 1)) / ((n - 2) * (n - 3)) * (m2 / m3) -
                              3 * Math.Pow(n - 1, 2) / ((n - 2) * (n - 3));
            return kurtosis;
        }

        /// <summary>
        /// Calculate the Kurtosis of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Kurtosis of the specified data.</returns>
        public static double? Kurtosis(IEnumerable<double?> data)
        {
            double? mean = Mean(data, MeanType.Arithmetic);
            double m1 = 0d, m2 = 0d;
            double n = data.Count();
            for (int i = 0; i < n; i++)
            {
                double? m = data.ElementAt(i) - mean;
                m1 = m1 + m.GetValueOrDefault().ToThePower(2);
                m2 = m2 + m.GetValueOrDefault().ToThePower(4);
            }

            double m3 = m1.ToThePower(2);
            double kurtosis = ((n + 1) * n * (n - 1)) / ((n - 2) * (n - 3)) * (m2 / m3) -
                              3 * Math.Pow(n - 1, 2) / ((n - 2) * (n - 3));
            return kurtosis;
        }

        #endregion

        #region | Interquartile Range |

        /// <summary>
        /// Calculate the Interquartile Range of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Interquartile Range of the specified data.</returns>
        public static double InterQuartileRange(IEnumerable<double> data)
        {
            double[] source = data.ToArray();
            return Percentile(source, 75) - Percentile(source, 25);
        }

        /// <summary>
        /// Calculate the Interquartile Range of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Interquartile Range of the specified data.</returns>
        public static double? InterQuartileRange(IEnumerable<double?> data)
        {
            double?[] source = data.ToArray();
            return Percentile(source, 75) - Percentile(source, 25);
        }

        #endregion

        #region | First Quartile |

        /// <summary>
        /// Calculate the First Quartile of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>First Quartile of the specified data.</returns>
        public static double FirstQuartile(IEnumerable<double> data)
        {
            double[] source = data.ToArray();
            return Percentile(source, 25);
        }

        /// <summary>
        /// Calculate the First Quartile of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>First Quartile of the specified data.</returns>
        public static double? FirstQuartile(IEnumerable<double?> data)
        {
            double?[] source = data.ToArray();
            return Percentile(source, 25);
        }

        #endregion

        #region | Third Quartile |

        /// <summary>
        /// Calculate the Third Quartile of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Third Quartile of the specified data.</returns>
        public static double ThirdQuartile(IEnumerable<double> data)
        {
            double[] source = data.ToArray();
            return Percentile(source, 75);
        }

        /// <summary>
        /// Calculate the Third Quartile of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Third Quartile of the specified data.</returns>
        public static double? ThirdQuartile(IEnumerable<double?> data)
        {
            double?[] source = data.ToArray();
            return Percentile(source, 75);
        }

        #endregion

        #region | Sum of Error |

        /// <summary>
        /// Calculate the Sum of Error of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Sum of Error of the specified data.</returns>
        public static double SumOfError(IEnumerable<double> data)
        {
            double soe = 0;
            for (int i = 0; i < data.Count(); i++)
            {
                double m = data.ElementAt(i) - Mean(data, MeanType.Arithmetic);
                soe += Math.Abs(m);
            }
            return soe;
        }

        /// <summary>
        /// Calculate the Sum of Error of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Sum of Error of the specified data.</returns>
        public static double? SumOfError(IEnumerable<double?> data)
        {
            double? soe = 0;
            for (int i = 0; i < data.Count(); i++)
            {
                double? m = data.ElementAt(i) - Mean(data, MeanType.Arithmetic);
                soe += Math.Abs(m.GetValueOrDefault());
            }
            return soe;
        }

        #endregion

        #region | Sum of Error Square |

        /// <summary>
        /// Calculate the Sum of Error Square of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Sum of Error Square of the specified data.</returns>
        public static double SumOfErrorSquare(IEnumerable<double> data)
        {
            double m1 = 0, m2 = 0;
            double mean = Mean(data, MeanType.Arithmetic);
            for (int i = 0; i < data.Count(); i++)
            {
                double m = data.ElementAt(i) - mean;
                m1 = m1 + Math.Abs(m);
                m2 += m.ToThePower(2);
            }
            return m2;
        }

        /// <summary>
        /// Calculate the Sum of Error Square of the specified data.
        /// </summary>
        /// <param name="data">The source data.</param>
        /// <returns>Sum of Error Square of the specified data.</returns>
        public static double? SumOfErrorSquare(IEnumerable<double?> data)
        {
            double m1 = 0, m2 = 0;
            double? mean = Mean(data, MeanType.Arithmetic);
            for (int i = 0; i < data.Count(); i++)
            {
                double? m = data.ElementAt(i) - mean;
                m1 = m1 + Math.Abs(m.GetValueOrDefault());
                m2 += m.GetValueOrDefault().ToThePower(2);
            }
            return m2;
        }

        #endregion
    }
}